import { SYSTEM_KEYS } from "../utils/storageKeys.js";
import config from "../config/index.js";

// 获取或生成浏览器实例ID
function getBrowserInstanceId() {
  const browserId =
    sessionStorage.getItem(SYSTEM_KEYS.BROWSER_INSTANCE_ID) ||
    "browser_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);

  if (!sessionStorage.getItem(SYSTEM_KEYS.BROWSER_INSTANCE_ID)) {
    sessionStorage.setItem(SYSTEM_KEYS.BROWSER_INSTANCE_ID, browserId);
  }

  return browserId;
}

class LiveSession {
  constructor(store) {
    this._wss = config.WS_URL;
    this._socket = null;
    this._gamestate = [];
    this._store = store;
    this._pingInterval = 30 * 1000; // 30 seconds between pings
    this._pingTimer = null;
    this._reconnectTimer = null;
    this._players = {}; // map of players connected to a session
    this._pings = {}; // map of player IDs to ping
    this._isInitializing = true; // 添加初始化标志
    // reconnect to previous session
    if (this._store.state.session.sessionId) {
      this.connect(this._store.state.session.sessionId);
    }
  }

  /**
   * Open a new session for the passed channel.
   * @param channel
   * @private
   */
  _open(channel) {
    this.disconnect();
    this._socket = new WebSocket(
      this._wss +
        channel +
        "/" +
        this._getConnectionUserId(),
    );
    this._socket.addEventListener("message", this._handleMessage.bind(this));
    this._socket.onopen = this._onOpen.bind(this);
    this._socket.onclose = (err) => {
      config.logger.debug("WebSocket连接关闭:", err.code, err.reason);
      this._socket = null;
      clearInterval(this._pingTimer);
      this._pingTimer = null;
      
      if (err.code !== 1000) {
        // connection interrupted, reconnect after 3 seconds
        config.logger.debug("连接异常断开，3秒后重连...");
        this._store.commit("session/setReconnecting", true);
        this._reconnectTimer = setTimeout(
          () => {
            config.logger.debug("尝试重新连接...");
            this.connect(channel);
          },
          3 * 1000,
        );
      } else {
        config.logger.debug("连接正常关闭");
        // 不自动清空sessionId，让用户保持会话状态
        // this._store.commit("session/setSessionId", "");
        if (err.reason) alert(err.reason);
      }
    };
  }

  /**
   * Send a message through the socket.
   * @param command
   * @param params
   * @private
   */
  _send(command, params) {
    if (this._socket && this._socket.readyState === 1) {
      this._socket.send(JSON.stringify([command, params]));
    }
  }

  /**
   * Send a message directly to a single playerId, if provided.
   * Otherwise broadcast it.
   * @param playerId player ID or "host", optional
   * @param command
   * @param params
   * @private
   */
  _sendDirect(playerId, command, params) {
    if (playerId) {
      this._send("direct", { [playerId]: [command, params] });
    } else {
      this._send(command, params);
    }
  }

  /**
   * Open event handler for socket.
   * @private
   */
  _onOpen() {
    config.logger.debug("Socket连接已建立");
    config.logger.debug("当前用户角色:", this._getCurrentUserRole() || "未知(使用本地状态)");
    config.logger.debug("当前玩家ID:", this._store.state.session.playerId);

    // 判断是否为说书人：优先使用服务器数据，回退到store状态
    const isStoryteller = this._isCurrentUserStoryteller() || this._store.state.session.isSpectator;
    
    if (isStoryteller) {
      config.logger.debug("说书人模式：发送游戏状态");
      // 说书人连接时发送完整的游戏状态给所有连接的玩家
      this.sendGamestate("", false, false);
    } else {
      config.logger.debug("玩家模式：请求游戏状态");
      this._sendDirect(
        "host",
        "getGamestate",
        this._store.state.session.playerId,
      );
    }
    
    // 延迟验证：等待服务器用户数据下发后重新检查角色
    setTimeout(() => {
      this._validateUserRoleFromServer();
    }, 1000);
    
    this._isInitializing = false; // 设置初始化完成
    this._ping();
  }

  /**
   * 获取当前用户的服务器角色
   * @private
   * @returns {string|null} 'storyteller', 'player', 'spectator' 或 null
   */
  _getCurrentUserRole() {
    const currentPlayerId = this._store.state.session.playerId;
    const currentUser = this._store.state.voiceChat.users?.get(currentPlayerId);
    return currentUser?.role || null;
  }

  /**
   * 检查当前用户是否为说书人
   * @private
   * @returns {boolean}
   */
  _isCurrentUserStoryteller() {
    return this._getCurrentUserRole() === 'storyteller';
  }

  /**
   * 检查当前用户是否为玩家
   * @private  
   * @returns {boolean}
   */
  _isCurrentUserPlayer() {
    return this._getCurrentUserRole() === 'player';
  }

  /**
   * 获取WebSocket连接的用户ID
   * 说书人使用 "host"，其他用户使用真实的playerId
   * @private
   * @returns {string}
   */
  _getConnectionUserId() {
    // 优先使用服务器角色数据
    const serverRole = this._getCurrentUserRole();
    if (serverRole) {
      return serverRole === 'storyteller' ? "host" : this._store.state.session.playerId;
    }
    
    // 服务器数据还没到达时，使用store中的session状态
    return this._store.state.session.isSpectator ? "host" : this._store.state.session.playerId;
  }

  /**
   * 验证服务器端用户角色数据
   * @private
   */
  _validateUserRoleFromServer() {
    const currentPlayerId = this._store.state.session.playerId;
    const currentUser = this._store.state.voiceChat.users?.get(currentPlayerId);
    
    if (currentUser) {
      const serverRole = currentUser.role;
      const clientExpectedRole = this._store.state.session.isSpectator ? 'storyteller' : 'player';
      
      config.logger.debug("🔍 验证用户角色:", {
        playerId: currentPlayerId,
        serverRole: serverRole,
        clientExpectedRole: clientExpectedRole,
        isConsistent: serverRole === clientExpectedRole
      });
      
      if (serverRole !== clientExpectedRole) {
        config.logger.warn("⚠️ 客户端和服务器端角色不一致，以服务器端为准:", {
          server: serverRole,
          client: clientExpectedRole
        });
      }
    } else {
      config.logger.debug("⏳ 服务器用户数据尚未下发，稍后重试");
      // 如果数据还没下发，再等一秒重试
      setTimeout(() => {
        this._validateUserRoleFromServer();
      }, 1000);
    }
  }

  /**
   * Send a ping message with player ID and ST flag.
   * @private
   */
  _ping() {
    if (!this._socket || this._socket.readyState !== WebSocket.OPEN) {
      config.logger.warn("Socket未连接，停止ping");
      clearTimeout(this._pingTimer);
      return;
    }
    
    this._handlePing();
    this._send("ping", [
      this._isCurrentUserStoryteller()
        ? this._store.state.session.playerId
        : Object.keys(this._players).length,
      "latency",
    ]);
    clearTimeout(this._pingTimer);
    this._pingTimer = setTimeout(this._ping.bind(this), this._pingInterval);
  }

  /**
   * Handle an incoming socket message.
   * @param data
   * @private
   */
  _handleMessage({ data }) {
    let command, params;
    try {
      [command, params] = JSON.parse(data);
      // 降噪：过滤高频消息的日志
      const noisy = new Set(['voice_speaking_state', 'voice_webrtc_signal', 'ping']);
      if (!noisy.has(command)) {
        config.logger.debug("收到socket消息:", command, params);
      }
    } catch (err) {
      config.logger.debug("unsupported socket message", data);
    }
    switch (command) {
      case "getGamestate":
        // 玩家请求游戏状态时，不包含角色信息
        this.sendGamestate(params, false, false);
        break;
      case "edition":
        this._updateEdition(params);
        break;
      case "fabled":
        this._updateFabled(params);
        break;
      case "gs":
        this._updateGamestate(params);
        // 如果包含语音状态快照，优先按快照进行一次性同步
        if (params && params.voiceChat && !this._isCurrentUserStoryteller()) {
          this._handleVoiceStateSnapshot({
            revision: 0,
            roles: {},
            publicChat: params.voiceChat.publicChat,
            privateChats: params.voiceChat.privateChats || {}
          });
        }
        break;
      case "player":
        this._updatePlayer(params);
        break;
      case "claim":
        this._updateSeat(params);
        break;
      case "ping":
        this._handlePing(params);
        break;
      case "nomination":
        if (!this._isCurrentUserStoryteller()) return;
        if (!params) {
          // create vote history record
          this._store.commit(
            "session/addHistory",
            this._store.state.players.players,
          );
        }
        this._store.commit("session/nomination", { nomination: params });
        break;
      case "swap":
        if (!this._isCurrentUserStoryteller()) return;
        this._store.commit("players/swap", params);
        break;
      case "move":
        if (!this._isCurrentUserStoryteller()) return;
        this._store.commit("players/move", params);
        break;
      case "remove":
        if (!this._isCurrentUserStoryteller()) return;
        this._store.commit("players/remove", params);
        break;
      case "marked":
        if (!this._isCurrentUserStoryteller()) return;
        this._store.commit("session/setMarkedPlayer", params);
        break;
      case "isNight":
        if (!this._isCurrentUserStoryteller()) return;
        this._store.commit("toggleNight", params);
        break;
      case "isVoteHistoryAllowed":
        if (!this._isCurrentUserStoryteller()) return;
        this._store.commit("session/setVoteHistoryAllowed", params);
        this._store.commit("session/clearVoteHistory");
        break;
      case "votingSpeed":
        if (!this._isCurrentUserStoryteller()) return;
        this._store.commit("session/setVotingSpeed", params);
        break;
      case "clearVoteHistory":
        if (!this._isCurrentUserStoryteller()) return;
        this._store.commit("session/clearVoteHistory");
        break;
      case "isVoteInProgress":
        if (!this._isCurrentUserStoryteller()) return;
        this._store.commit("session/setVoteInProgress", params);
        break;
      case "vote":
        this._handleVote(params);
        break;
      case "lock":
        this._handleLock(params);
        break;
      case "bye":
        this._handleBye(params);
        break;
      case "pronouns":
        this._updatePlayerPronouns(params);
        break;
      case "reset":
        config.logger.debug("收到重置消息:", params);
        config.logger.debug("当前角色:", this._getCurrentUserRole() || "未知");
        this._handleReset(params);
        break;
      case "private_message":
        this._handlePrivateMessage(params);
        break;
      case "private_message_delivered":
        this._handleMessageDelivered(params);
        break;
      case "voice_chat_join":
        config.logger.debug('🎯 Socket接收到voice_chat_join消息，准备处理...');
        this._handleVoiceChatJoin(params);
        break;
      case "voice_chat_leave":
        this._handleVoiceChatLeave(params);
        break;
      case "voice_private_request":
        this._handleVoicePrivateRequest(params);
        break;
      case "voice_private_response":
        this._handleVoicePrivateResponse(params);
        break;
      case "voice_private_invite":
        this._handleVoicePrivateInvite(params);
        break;
      case "voice_private_invite_response":
        this._handleVoicePrivateInviteResponse(params);
        break;
      case "voice_private_create_room":
        this._handleVoicePrivateCreateRoom(params);
        break;
      case "voice_private_leave":
        this._handleVoicePrivateLeave(params);
        break;
      case "voice_storyteller_control":
        this._handleVoiceStorytellerControl(params);
        break;
      case "voice_speaking_state":
        this._handleVoiceSpeakingState(params);
        break;
      case "voice_webrtc_signal":
        this._handleVoiceWebRTCSignal(params);
        break;
      case "voice_role_update":
        this._handleVoiceRoleUpdate(params);
        break;
      case "voice_state_snapshot":
        this._handleVoiceStateSnapshot(params);
        break;
      case "users_snapshot":
        this._handleUsersSnapshot(params);
        break;
      case "user_joined":
        this._handleUserJoined(params);
        break;
      case "user_left":
        this._handleUserLeft(params);
        break;
      case "user_update":
        this._handleUserUpdate(params);
        break;
      case "rooms_snapshot":
        this._handleRoomsSnapshot(params);
        break;
      case "room_update":
        this._handleRoomUpdate(params);
        break;
      case "voice_roles_snapshot":
        this._handleVoiceRolesSnapshot(params);
        break;
      case "voice_private_room_update":
        this._handleVoicePrivateRoomUpdate(params);
        break;
      case "voice_private_join_request":
        this._handleVoicePrivateJoinRequest(params);
        break;
      case "voice_private_join_response":
        this._handleVoicePrivateJoinResponse(params);
        break;
      case "voice_public_update":
        this._handleVoicePublicUpdate(params);
        break;
      default:
        config.logger.debug("未处理的消息类型:", command);
        break;
    }
  }

  /**
   * Connect to a new live session, either as host or spectator.
   * Set a unique playerId if there isn't one yet.
   * @param channel
   */
  connect(channel) {
    // 只有在没有玩家ID时才生成新的玩家ID
    if (!this._store.state.session.playerId) {
      let playerId;
      if (window.location.hash.substr(1)) {
        // 使用URL参数作为玩家ID，基于hash生成稳定的ID
        const hash = window.location.hash.substr(1);
        // 使用hash作为基础，添加一个固定的后缀来区分不同浏览器实例
        // 但不使用时间戳，这样刷新时ID保持一致
        const browserId = getBrowserInstanceId();
        playerId = hash + "_" + browserId;
      } else {
        // 生成基于会话ID的唯一玩家ID
        playerId =
          "player_" +
          Date.now() +
          "_" +
          Math.random().toString(36).substr(2, 9);
      }

      config.logger.debug("生成新玩家ID:", playerId);
      this._store.commit("session/setPlayerId", playerId);
    } else {
      config.logger.debug("使用现有玩家ID:", this._store.state.session.playerId);
    }

    // 重置连接状态
    this._pings = {};
    this._store.commit("session/setPlayerCount", 0);
    this._store.commit("session/setPing", 0);
    this._store.commit("session/setReconnecting", false);
    // 完全依赖服务器用户数据，不再使用本地_isSpectator状态

    config.logger.debug("开始连接，玩家ID:", this._store.state.session.playerId);
    config.logger.debug("当前角色模式:", this._getCurrentUserRole() || "本地状态");
    this._open(channel);
  }

  /**
   * Close the current session, if any.
   */
  disconnect() {
    this._pings = {};
    this._store.commit("session/setPlayerCount", 0);
    this._store.commit("session/setPing", 0);
    this._store.commit("session/setReconnecting", false);
    clearTimeout(this._reconnectTimer);
    if (this._socket) {
      // 说书人断开时通知服务器
      if (this._isCurrentUserStoryteller() || this._store.state.session.isSpectator) {
        this._sendDirect("host", "bye", this._store.state.session.playerId);
      }
      this._socket.close(1000);
      this._socket = null;
    }
  }

  /**
   * Publish the current gamestate.
   * Optional param to reduce traffic. (send only player data)
   * @param playerId
   * @param isLightweight
   */
  sendGamestate(playerId = "", isLightweight = false, includeRoles = false) {
    // 完全依赖服务器端用户数据判断权限
    const currentPlayerId = this._store.state.session.playerId;
    const currentUser = this._store.state.voiceChat.users?.get(currentPlayerId);
    const userRole = currentUser?.role;
    
    // 必须等待服务器用户数据
    if (!currentUser) {
      config.logger.warn("❌ 等待服务器用户数据，无法发送游戏状态");
      return;
    }
    
    // 只有说书人可以发送游戏状态
    if (userRole !== 'storyteller') {
      config.logger.warn("❌ 只有说书人可以发送游戏状态");
      return;
    }

    config.logger.debug("=== sendGamestate 被调用 ===");
    config.logger.debug("playerId:", playerId);
    config.logger.debug("isLightweight:", isLightweight);
    config.logger.debug("includeRoles:", includeRoles);
    config.logger.debug("当前用户角色:", userRole);

    // 检查目标玩家
    let targetPlayer = null;
    if (playerId) {
      targetPlayer = this._store.state.players.players.find(
        (p) => p.id === playerId,
      );
    }

    this._gamestate = this._store.state.players.players.map((player) => {
      // 只向对应玩家发送角色信息
      const isTargetPlayer = playerId && player.id === playerId;
      const hasRole =
        includeRoles &&
        player.role &&
        player.role.id &&
        (isTargetPlayer || !playerId); // 如果是广播或目标玩家才发送角色

      config.logger.debug(`玩家 ${player.name}:`, {
        hasRole: hasRole,
        roleId: player.role?.id,
        roleTeam: player.role?.team,
        isTargetPlayer: isTargetPlayer,
        playerId: playerId,
        includeRoles: includeRoles,
      });

      return {
        name: player.name,
        id: player.id,
        isDead: player.isDead,
        isVoteless: player.isVoteless,
        pronouns: player.pronouns,
        isStoryteller: player.isStoryteller || false,
        // 只向对应玩家发送角色信息
        ...(hasRole ? { roleId: player.role.id } : {}),
      };
    });

    config.logger.debug("生成的gamestate:", this._gamestate);

    if (isLightweight) {
      this._sendDirect(playerId, "gs", {
        gamestate: this._gamestate,
        isLightweight,
      });
    } else {
      const { session, grimoire } = this._store.state;
      const { fabled, bluffs } = this._store.state.players;

      // 检查目标玩家是否为恶魔
      let shouldSendBluffs = false;
      if (playerId && targetPlayer) {
        shouldSendBluffs =
          targetPlayer.role && targetPlayer.role.team === "demon";
        config.logger.debug(
          `检查恶魔伪装发送: ${targetPlayer.name}, 是否为恶魔: ${shouldSendBluffs}`,
        );
      } else {
        // 如果没有指定playerId，说明是广播给所有玩家
        // 在这种情况下，所有玩家都会收到，但只有恶魔玩家会处理
        shouldSendBluffs = true;
        config.logger.debug("广播模式，所有玩家都会收到恶魔伪装信息");
      }

      this.sendEdition(playerId);
      
      // 获取语音聊天状态
      const voiceChatState = this._store.state.voiceChat;
      
      // 收集所有活跃的私聊房间
      const privateChats = {};
      if (voiceChatState.chatRooms.private instanceof Map) {
        voiceChatState.chatRooms.private.forEach((room, roomId) => {
          if (room.active && room.participants && Array.isArray(room.participants) && room.participants.length >= 2) {
            privateChats[roomId] = {
              active: room.active,
              participants: room.participants,
              muted: room.muted || false,
              createdAt: room.createdAt || Date.now()
            };
          }
        });
      }
      
      const voiceChatData = {
        publicChat: {
          active: voiceChatState.chatRooms.public.active,
          participants: voiceChatState.chatRooms.public.participants || []
        },
        privateChats: privateChats
      };
      
      config.logger.debug('📢 发送语音聊天状态给玩家:', playerId, voiceChatData);
      
      this._sendDirect(playerId, "gs", {
        gamestate: this._gamestate,
        isNight: grimoire.isNight,
        isVoteHistoryAllowed: session.isVoteHistoryAllowed,
        nomination: session.nomination,
        votingSpeed: session.votingSpeed,
        lockedVote: session.lockedVote,
        isVoteInProgress: session.isVoteInProgress,
        markedPlayer: session.markedPlayer,
        fabled: fabled.map((f) => (f.isCustom ? f : { id: f.id })),
        ...(session.nomination ? { votes: session.votes } : {}),
        // 只向恶魔玩家发送bluffs信息
        ...(shouldSendBluffs ? { bluffs: bluffs } : {}),
        // 添加语音聊天状态
        voiceChat: voiceChatData,
      });
    }
  }

  /**
   * Update the gamestate based on incoming data.
   * @param data
   * @private
   */
  _updateGamestate(data) {
    // 完全依赖服务器端用户数据判断权限
    const currentPlayerId = this._store.state.session.playerId;
    const currentUser = this._store.state.voiceChat.users?.get(currentPlayerId);
    const userRole = currentUser?.role;
    
    // 如果没有服务器用户数据，暂时允许更新（兼容初始连接）
    if (currentUser && userRole === 'storyteller') {
      config.logger.debug("说书人不需要更新游戏状态");
      return;
    }
    const {
      gamestate,
      isLightweight,
      isNight,
      isVoteHistoryAllowed,
      nomination,
      votingSpeed,
      votes,
      lockedVote,
      isVoteInProgress,
      markedPlayer,
      fabled,
      bluffs,
      voiceChat,
    } = data;
    const players = this._store.state.players.players;
    // adjust number of players
    if (players.length < gamestate.length) {
      for (let x = players.length; x < gamestate.length; x++) {
        this._store.commit("players/add", gamestate[x].name);
        // 立即更新新添加玩家的ID和isStoryteller属性
        const newPlayer = players[x];
        if (newPlayer) {
          this._store.commit("players/update", { 
            player: newPlayer, 
            property: "id", 
            value: gamestate[x].id 
          });
          this._store.commit("players/update", { 
            player: newPlayer, 
            property: "isStoryteller", 
            value: gamestate[x].isStoryteller || false 
          });
        }
      }
    } else if (players.length > gamestate.length) {
      for (let x = players.length; x > gamestate.length; x--) {
        this._store.commit("players/remove", x - 1);
      }
    }
    // update status for each player
    gamestate.forEach((state, x) => {
      const player = players[x];
      const { roleId } = state;

      config.logger.debug(`处理玩家 ${player.name} 的状态:`, {
        hasRoleId: !!roleId,
        roleId: roleId,
        currentRoleId: player.role?.id,
        isCurrentPlayer: player.id === this._store.state.session.playerId,
      });

      // update relevant properties
      ["name", "id", "isDead", "isVoteless", "pronouns", "isStoryteller"].forEach((property) => {
        const value = state[property];
        if (player[property] !== value) {
          this._store.commit("players/update", { player, property, value });
        }
      });

      // roles are special, because of travelers
      // 只有在明确接收到roleId时才处理角色信息
      if (roleId && player.role.id !== roleId) {
        config.logger.debug(`🎭 接收到角色信息: ${roleId} for player ${player.name}`);
        
        // 尝试从不同的角色存储中查找角色
        let role = this._store.state.roles.get(roleId);
        if (!role) {
          role = this._store.getters.rolesJSONbyId.get(roleId);
        }
        
        if (role) {
          config.logger.debug(`✅ 找到角色对象:`, {
            roleId: role.id,
            roleName: role.name,
            roleTeam: role.team,
            playerName: player.name
          });
          this._store.commit("players/update", {
            player,
            property: "role",
            value: role,
          });
        } else {
          config.logger.error(`❌ 无法找到角色: ${roleId} for player ${player.name}`);
          config.logger.error(`当前角色存储状态:`, {
            rolesCount: this._store.state.roles.size,
            rolesKeys: Array.from(this._store.state.roles.keys()).slice(0, 5),
            gettersRolesCount: this._store.getters.rolesJSONbyId?.size,
          });
        }
      } else if (!roleId && player.role.team === "traveler") {
        this._store.commit("players/update", {
          player,
          property: "role",
          value: {},
        });
      }
    });
    if (!isLightweight) {
      this._store.commit("toggleNight", !!isNight);
      this._store.commit(
        "session/setVoteHistoryAllowed",
        !!isVoteHistoryAllowed,
      );
      this._store.commit("session/nomination", {
        nomination: !!nomination,
        votes: votes || [],
        votingSpeed: votingSpeed || 3000,
        lockedVote: lockedVote || 0,
        isVoteInProgress: !!isVoteInProgress,
      });
      this._store.commit("session/setMarkedPlayer", markedPlayer || -1);
      if (fabled) {
        this._store.commit("players/setFabled", { fabled });
      }
      // 处理bluffs信息 - 只有恶魔玩家才会处理
      if (bluffs) {
        // 检查当前玩家是否为恶魔
        const currentPlayer = this._store.state.players.players.find(
          (p) => p.id === this._store.state.session.playerId,
        );
        const isCurrentPlayerDemon =
          currentPlayer &&
          currentPlayer.role &&
          currentPlayer.role.team === "demon";

        config.logger.debug("检查恶魔伪装处理:", {
          currentPlayerId: this._store.state.session.playerId,
          currentPlayer: currentPlayer,
          currentPlayerRole: currentPlayer?.role,
          isCurrentPlayerDemon: isCurrentPlayerDemon,
          bluffs: bluffs,
        });

        if (isCurrentPlayerDemon) {
          config.logger.debug("恶魔玩家收到恶魔伪装信息:", bluffs);
          // 确保bluffs是数组格式
          if (Array.isArray(bluffs)) {
            bluffs.forEach((bluff, index) => {
              this._store.commit("players/setBluff", {
                index,
                role: bluff,
              });
            });
          } else {
            this._store.commit("players/setBluff", { bluffs });
          }
        } else {
          config.logger.debug("非恶魔玩家收到bluffs信息，忽略处理");
        }
      }
    }
    
    // 处理语音聊天状态同步
    if (voiceChat) {
      config.logger.debug('🎯 收到语音聊天状态同步:', voiceChat);
      
      if (voiceChat.publicChat) {
        config.logger.debug('📢 更新公共语音聊天状态:', voiceChat.publicChat);
        
        // 更新公共语音聊天状态
        this._store.commit('voiceChat/updateChatRoom', {
          type: 'public',
          data: {
            active: voiceChat.publicChat.active,
            participants: voiceChat.publicChat.participants || []
          }
        });
        
        // 触发事件通知VoiceChatPanel组件
        if (window.voiceChatEventBus) {
          window.voiceChatEventBus.$emit('voice-chat-state-synced', {
            type: 'public',
            participants: voiceChat.publicChat.participants || []
          });
          config.logger.debug('📢 已触发voice-chat-state-synced事件');
        }
      }
      
      // 处理私聊房间状态同步
      if (voiceChat.privateChats) {
        config.logger.debug('🔒 同步私聊房间状态:', voiceChat.privateChats);
        
        // 遍历所有私聊房间并更新状态
        Object.entries(voiceChat.privateChats).forEach(([roomId, roomData]) => {
          config.logger.debug(`🔒 更新私聊房间 ${roomId}:`, roomData);
          
          this._store.commit('voiceChat/updateChatRoom', {
            type: 'private',
            roomId: roomId,
            data: {
              active: roomData.active,
              participants: roomData.participants || [],
              muted: roomData.muted || false,
              createdAt: roomData.createdAt || Date.now()
            }
          });
        });
        
        // 通知VoiceChatPanel组件有私聊房间需要同步
        if (window.voiceChatEventBus && Object.keys(voiceChat.privateChats).length > 0) {
          // 为每个房间发送创建事件
          Object.entries(voiceChat.privateChats).forEach(([roomId, roomData]) => {
            window.voiceChatEventBus.$emit('private-room-created', {
              roomId: roomId,
              participants: roomData.participants,
              createdAt: roomData.createdAt
            });
          });
          config.logger.debug(`🔒 已触发 ${Object.keys(voiceChat.privateChats).length} 个private-room-created事件`);
        }
      }
    }
  }

  /**
   * Publish an edition update. ST only
   * @param playerId
   */
  sendEdition(playerId = "") {
    // 只有玩家可以发送版本信息，说书人不需要
    if (this._isCurrentUserStoryteller()) return;
    const { edition } = this._store.state;
    let roles;
    if (!edition.isOfficial) {
      roles = this._store.getters.customRolesStripped;
    }
    this._sendDirect(playerId, "edition", {
      edition: edition.isOfficial ? { id: edition.id } : edition,
      ...(roles ? { roles } : {}),
    });
  }

  /**
   * Update edition and roles for custom editions.
   * @param edition
   * @param roles
   * @private
   */
  _updateEdition({ edition, roles }) {
    if (!this._isCurrentUserStoryteller()) return;
    this._store.commit("setEdition", edition);
    if (roles) {
      this._store.commit("setCustomRoles", roles);
      
      // 过滤掉无效的角色对象，只检查有效的角色
      const validRoles = roles.filter(role => role && role.id && typeof role.id === 'string' && role.id.trim() !== '');
      
      if (this._store.state.roles.size !== validRoles.length) {
        const missing = [];
        validRoles.forEach(({ id }) => {
          if (!this._store.state.roles.get(id)) {
            missing.push(id);
          }
        });
        
        // 只有当确实有缺失的角色时才显示警告
        if (missing.length > 0) {
          config.logger.warn("Missing custom roles:", missing);
          alert(
            `This session contains custom characters that can't be found. ` +
              `Please load them before joining! ` +
              `Missing roles: ${missing.join(", ")}`,
          );
          this.disconnect();
          this._store.commit("toggleModal", "edition");
        }
      }
    }
  }

  /**
   * Publish a fabled update. ST only
   */
  sendFabled() {
    if (this._isCurrentUserStoryteller()) return;
    const { fabled } = this._store.state.players;
    this._send(
      "fabled",
      fabled.map((f) => (f.isCustom ? f : { id: f.id })),
    );
  }

  /**
   * Update fabled roles.
   * @param fabled
   * @private
   */
  _updateFabled(fabled) {
    if (!this._isCurrentUserStoryteller()) return;
    this._store.commit("players/setFabled", {
      fabled: fabled.map((f) => this._store.state.fabled.get(f.id) || f),
    });
  }

  /**
   * Publish a player update.
   * @param player
   * @param property
   * @param value
   */
  sendPlayer({ player, property, value }) {
    config.logger.debug("=== sendPlayer 被调用 ===");
    config.logger.debug("player:", player.name);
    config.logger.debug("property:", property);
    config.logger.debug("value:", value);
    config.logger.debug("当前角色:", this._getCurrentUserRole());

    if (this._isCurrentUserStoryteller() || property === "reminders") return;

    // 角色信息不通过sendPlayer发送，只在distributeRoles时发送
    if (property === "role") {
      config.logger.debug("角色信息不通过sendPlayer发送，跳过");
      return;
    }

    const index = this._store.state.players.players.indexOf(player);
    this._send("player", { index, property, value });
  }

  /**
   * Update a player based on incoming data. Player only.
   * @param index
   * @param property
   * @param value
   * @private
   */
  _updatePlayer({ index, property, value }) {
    if (!this._isCurrentUserStoryteller()) return;
    const player = this._store.state.players.players[index];
    if (!player) return;

    config.logger.debug(`收到玩家更新: ${player.name}, 属性: ${property}, 值:`, value);

    // special case where a player stops being a traveler
    if (property === "role") {
      config.logger.debug(`处理角色更新: ${player.name}, 角色ID: ${value}`);
      if (!value && player.role.team === "traveler") {
        // reset to an unknown role
        config.logger.debug(`重置旅行者角色: ${player.name}`);
        this._store.commit("players/update", {
          player,
          property: "role",
          value: {},
        });
      } else {
        // load role, first from session, the global, then fail gracefully
        const role =
          this._store.state.roles.get(value) ||
          this._store.getters.rolesJSONbyId.get(value) ||
          {};
        config.logger.debug(`更新玩家角色: ${player.name}, 角色ID: ${value} ->`, role);
        this._store.commit("players/update", {
          player,
          property: "role",
          value: role,
        });
      }
    } else {
      // just update the player otherwise
      config.logger.debug(`更新玩家属性: ${player.name}, ${property}: ${value}`);
      this._store.commit("players/update", { player, property, value });
    }
  }

  /**
   * Publish a player pronouns update
   * @param player
   * @param value
   * @param isFromSockets
   */
  sendPlayerPronouns({ player, value, isFromSockets }) {
    //send pronoun only for the seated player or storyteller
    //Do not re-send pronoun data for an update that was recieved from the sockets layer
    if (
      isFromSockets ||
      (this._isCurrentUserStoryteller() && this._store.state.session.playerId !== player.id)
    )
      return;
    const index = this._store.state.players.players.indexOf(player);
    this._send("pronouns", [index, value]);
  }

  /**
   * Update a pronouns based on incoming data.
   * @param index
   * @param value
   * @private
   */
  _updatePlayerPronouns([index, value]) {
    const player = this._store.state.players.players[index];

    this._store.commit("players/update", {
      player,
      property: "pronouns",
      value,
      isFromSockets: true,
    });
  }

  /**
   * Handle a ping message by another player / storyteller
   * @param playerIdOrCount
   * @param latency
   * @private
   */
  _handlePing([playerIdOrCount = 0, latency] = []) {
    const now = new Date().getTime();
    if (!this._isCurrentUserStoryteller()) {
      // 记录超时离线的玩家，稍后统一清理
      const disconnectedPlayers = [];
      // remove players that haven't sent a ping in twice the timespan
      for (let player in this._players) {
        if (now - this._players[player] > this._pingInterval * 2) {
          disconnectedPlayers.push(player);
          delete this._players[player];
          delete this._pings[player];
        }
      }
      // remove claimed seats from players that are no longer connected
      this._store.state.players.players.forEach((player) => {
        if (player.id && !this._players[player.id]) {
          this._store.commit("players/update", {
            player,
            property: "id",
            value: "",
          });
        }
      });
      // store new player data
      if (playerIdOrCount) {
        this._players[playerIdOrCount] = now;
        const ping = parseInt(latency, 10);
        if (ping && ping > 0 && ping < 30 * 1000) {
          // ping to Players
          this._pings[playerIdOrCount] = ping;
          const pings = Object.values(this._pings);
          this._store.commit(
            "session/setPing",
            Math.round(pings.reduce((a, b) => a + b, 0) / pings.length),
          );
        }
      }

      // 清理语音聊天房间中离线玩家（公聊与私聊），并广播离开事件
      if (disconnectedPlayers.length > 0) {
        disconnectedPlayers.forEach((pid) => this._cleanupDisconnectedVoice(pid));
      }
    } else if (latency) {
      // ping to ST
      this._store.commit("session/setPing", parseInt(latency, 10));
    }
    // update player count
    if (!this._isCurrentUserStoryteller() || playerIdOrCount) {
      this._store.commit(
        "session/setPlayerCount",
        this._isCurrentUserStoryteller() ? playerIdOrCount : Object.keys(this._players).length,
      );
    }
  }

  /**
   * 清理断线玩家在语音聊天中的状态，并广播给所有客户端
   * @param {string} playerId 
   * @private
   */
  _cleanupDisconnectedVoice(playerId) {
    try {
      const voiceChat = this._store.state.voiceChat;
      const sessionId = this._store.state.session.sessionId;

      // 公共聊天处理
      const publicParticipants = voiceChat.chatRooms.public.participants || [];
      if (publicParticipants.includes(playerId)) {
        const newParticipants = publicParticipants.filter((id) => id !== playerId);
        this._store.commit('voiceChat/updateChatRoom', {
          type: 'public',
          data: { participants: newParticipants, active: newParticipants.length > 0 }
        });

        // 广播离开公聊
        this._send('voice_chat_leave', {
          type: 'public',
          playerId,
          playerName: playerId,
          sessionId,
          action: 'leave'
        });
      }

      // 私聊房间处理
      if (voiceChat.chatRooms.private instanceof Map) {
        voiceChat.chatRooms.private.forEach((room, roomId) => {
          if (room && Array.isArray(room.participants) && room.participants.includes(playerId)) {
            const newParticipants = room.participants.filter((id) => id !== playerId);
            const willClose = newParticipants.length === 0;

            this._store.commit('voiceChat/updateChatRoom', {
              type: 'private',
              roomId,
              data: { participants: newParticipants, active: !willClose }
            });

            // 广播该玩家离开私聊
            this._send('voice_private_leave', {
              roomId,
              playerId,
              playerName: playerId,
              sessionId
            });

            // 如果房间已空，广播房间关闭
            if (willClose) {
              this._send('voice_private_room_update', {
                action: 'closed',
                roomId
              });
            }
          }
        });
      }
    } catch (error) {
      config.logger.error('清理断线玩家语音状态失败:', error);
    }
  }

  /**
   * Handle a player leaving the sessions. ST only
   * @param playerId
   * @private
   */
  _handleBye(playerId) {
    if (this._isCurrentUserStoryteller()) return;
    delete this._players[playerId];
    this._store.commit(
      "session/setPlayerCount",
      Object.keys(this._players).length,
    );
  }

  /**
   * Handle receiving a private message
   * @param params message data
   * @private
   */
  _handlePrivateMessage(params) {

    
    try {
      const { senderId, receiverId, content, timestamp, id, roomId } = params;
      
      // 验证消息格式
      if (!senderId || !receiverId || !content || !id) {
        config.logger.warn("私聊消息格式不正确:", params);
        return;
      }
      
      // 验证房间ID
      if (roomId !== this._store.state.session.sessionId) {
        config.logger.warn("消息房间ID不匹配:", roomId, this._store.state.session.sessionId);
        return;
      }
      
      // 构造消息对象
      const message = {
        id,
        content,
        senderId,
        receiverId,
        timestamp: timestamp || Date.now(),
        isStoryteller: senderId === 'storyteller',
        isRead: false,
        chatType: 'private'
      };
      
      // 如果是说书人，处理来自玩家的消息
      if (this._isCurrentUserStoryteller() && receiverId === 'storyteller') {

        
        this._store.dispatch('chat/receivePrivateMessage', {
          senderId,
          message
        });
        
        // 发送消息送达确认
        this._send("private_message_delivered", { 
          messageId: id, 
          senderId,
          receiverId: 'storyteller'
        });
      }
      // 如果是玩家，处理来自说书人的消息
      else if (!this._isCurrentUserStoryteller() && senderId === 'storyteller' && receiverId === this._store.state.session.playerId) {

        
        this._store.dispatch('chat/receivePrivateMessage', {
          senderId: 'storyteller',
          message
        });
        
        // 发送消息送达确认
        this._send("private_message_delivered", { 
          messageId: id, 
          senderId: 'storyteller',
          receiverId: this._store.state.session.playerId
        });
      }
      else {
        // 只在明显错误时才记录
        if (senderId === 'storyteller' && this._isCurrentUserStoryteller()) {
          config.logger.warn('❌ 玩家ID不匹配:', {
            receiverId, 
            myPlayerId: this._store.state.session.playerId
          });
        }
      }
      
    } catch (error) {
      config.logger.error("处理私聊消息失败:", error);
    }
  }

  /**
   * Handle message delivery confirmation
   * @param params delivery confirmation data
   * @private
   */
  _handleMessageDelivered() {
    try {
      // 可以在这里处理消息送达状态
      // 比如显示"已送达"状态
    } catch (error) {
      config.logger.error("处理消息送达确认失败:", error);
    }
  }

  /**
   * Handle voice chat join event
   * @param params voice chat join data
   * @private
   */
  _handleVoiceChatJoin(params) {
    try {
      config.logger.debug("🎯 Socket收到语音聊天加入事件:", params);
      
      // 验证sessionId - 只处理相同游戏房间的语音聊天消息
      if (params.sessionId && params.sessionId !== this._store.state.session.sessionId) {
        config.logger.debug("🚫 语音聊天sessionId不匹配，跳过处理:", {
          received: params.sessionId,
          current: this._store.state.session.sessionId
        });
        return;
      }
      
      if (params.type === 'public') {
        // 获取当前参与者列表
        const currentParticipants = this._store.state.voiceChat.chatRooms.public.participants || [];
        config.logger.debug("📋 当前公共聊天参与者:", currentParticipants);
        
        // 如果玩家不在列表中，添加到列表
        if (!currentParticipants.includes(params.playerId)) {
          const newParticipants = [...currentParticipants, params.playerId];
          config.logger.debug("➕ 添加玩家到参与者列表:", newParticipants);
          
          // 更新Vuex状态
          this._store.commit('voiceChat/updateChatRoom', {
            type: 'public',
            data: {
              active: true,
              participants: newParticipants
            }
          });
          config.logger.debug("✅ Vuex状态已更新");

          // 同步显示名优先级：服务器角色快照 > 入座名 > 服务端提供的displayName > 访客
          const players = this._store.state.players.players || [];
          const seated = players.find(p => p.id === params.playerId);
          const roleMap = this._store.state.voiceChat.roles;
          const role = roleMap && roleMap.get && roleMap.get(params.playerId);
          let displayName;
          if (role === 'storyteller' || params.playerId === 'host' || params.isStoryteller === true) {
            const savedName = localStorage.getItem('storyteller_name') || '';
            const star = localStorage.getItem('storyteller_star_level') || 'trainee';
            const starIcon = { trainee: '🌟', one_star: '⭐', two_star: '⭐⭐' }[star] || '🌟';
            displayName = savedName.trim() ? `【${starIcon}】说书人·${savedName.trim()}` : `【${starIcon}】说书人`;
            // 确保角色也登记
            this._store.commit('voiceChat/setRole', { playerId: params.playerId, role: 'storyteller' });
          } else if (seated && seated.name) {
            displayName = seated.name;
          } else if (params.displayName && params.displayName.trim()) {
            displayName = params.displayName.trim();
          } else {
            displayName = '【访客】';
          }
          this._store.commit('voiceChat/setDisplayName', {
            playerId: params.playerId,
            displayName
          });

          // 安全同步：当收到加入公聊的广播（尤其是说书人从私聊退回公聊）时，确保该玩家不再被标记在任何私聊房间中
          try {
            const vc = this._store.state.voiceChat;
            if (vc && vc.chatRooms && vc.chatRooms.private instanceof Map) {
              const roomsToUpdate = [];
              vc.chatRooms.private.forEach((room, roomId) => {
                if (room && Array.isArray(room.participants) && room.participants.includes(params.playerId)) {
                  const newParticipants = room.participants.filter(id => id !== params.playerId);
                  roomsToUpdate.push({ roomId, newParticipants });
                }
              });
              roomsToUpdate.forEach(({ roomId, newParticipants }) => {
                this._store.commit('voiceChat/updateChatRoom', {
                  type: 'private',
                  roomId,
                  data: { participants: newParticipants, active: newParticipants.length > 0 }
                });
              });
            }
          } catch (e) { /* 忽略 */ }
          
          // 触发事件，通知语音聊天组件有新玩家加入
          if (window.voiceChatEventBus) {
            // 通知其他客户端处理与新玩家的P2P连接
            window.voiceChatEventBus.$emit('player-joined-public-chat', {
              playerId: params.playerId,
              playerName: params.playerName || params.playerId
            });
            // 若本端即为加入者（如说书人退出私聊后被后端广播回到公聊），主动加入公聊语音房间
            if (this._store.state.session.playerId === params.playerId) {
              window.voiceChatEventBus.$emit('self-joined-public-chat', {
                playerId: params.playerId
              });
            }
            config.logger.debug("📢 已触发player-joined-public-chat事件");
          } else {
            config.logger.warn("⚠️ voiceChatEventBus不存在");
          }
          
          config.logger.debug(`🎉 玩家 ${params.playerName || params.playerId} 加入了公共语音聊天`);
        } else {
          config.logger.debug("ℹ️ 玩家已在公共聊天中，跳过添加");
        }
      } else {
        config.logger.debug("ℹ️ 非公共聊天类型，跳过处理");
      }
    } catch (error) {
      config.logger.error("❌ 处理语音聊天加入事件失败:", error);
    }
  }

  /**
   * Handle voice chat leave event
   * @param params voice chat leave data
   * @private
   */
  _handleVoiceChatLeave(params) {
    try {
      config.logger.debug("🎯 Socket收到语音聊天离开事件:", params);
      
      // 验证sessionId - 只处理相同游戏房间的语音聊天消息
      if (params.sessionId && params.sessionId !== this._store.state.session.sessionId) {
        config.logger.debug("🚫 语音聊天sessionId不匹配，跳过处理:", {
          received: params.sessionId,
          current: this._store.state.session.sessionId
        });
        return;
      }
      
      if (params.type === 'public') {
        // 获取当前参与者列表
        const currentParticipants = this._store.state.voiceChat.chatRooms.public.participants || [];
        
        // 从列表中移除玩家
        const newParticipants = currentParticipants.filter(id => id !== params.playerId);
        
        // 更新Vuex状态
        this._store.commit('voiceChat/updateChatRoom', {
          type: 'public',
          data: {
            participants: newParticipants
          }
        });
        
        config.logger.debug(`🎉 玩家 ${params.playerName || params.playerId} 离开了公共语音聊天`);
      }
    } catch (error) {
      config.logger.error("处理语音聊天离开事件失败:", error);
    }
  }

  /**
   * Handle voice private chat request
   * @param params private chat request data
   * @private
   */
  _handleVoicePrivateRequest(params) {
    try {
      config.logger.debug("收到私人语音聊天申请:", params);
      
      // 只有目标玩家才处理申请
      if (params.request.toId === this._store.state.session.playerId) {
        this._store.commit('voiceChat/addPrivateChatRequest', params.request);
        config.logger.debug(`收到来自 ${params.fromName} 的私人语音聊天申请`);
      }
    } catch (error) {
      config.logger.error("处理私人语音聊天申请失败:", error);
    }
  }

  /**
   * Handle voice private chat response
   * @param params private chat response data
   * @private
   */
  _handleVoicePrivateResponse(params) {
    try {
      config.logger.debug("收到私人语音聊天回应:", params);
      
      // 更新申请状态
      this._store.commit('voiceChat/updatePrivateChatRequest', {
        requestId: params.requestId,
        status: params.approved ? 'approved' : 'rejected'
      });
      
      // 如果同意，创建私人聊天房间
      if (params.approved && params.roomId) {
        this._store.commit('voiceChat/updateChatRoom', {
          type: 'private',
          roomId: params.roomId,
          data: {
            active: true,
            participants: [params.fromId, params.toId],
            muted: false,
            createdAt: Date.now()
          }
        });
      }
      
      config.logger.debug(`私人语音聊天申请被${params.approved ? '同意' : '拒绝'}`);
    } catch (error) {
      config.logger.error("处理私人语音聊天回应失败:", error);
    }
  }

  /** 收到多人私聊邀请（目标玩家端） */
  _handleVoicePrivateInvite(params) {
    try {
      const { roomId, ownerId, ownerName, snapshot } = params || {};
      // 仅被邀请玩家处理：设置中心模态的邀请信息
      this._store.commit('voiceChat/setIncomingInvite', {
        roomId,
        ownerId,
        ownerName: ownerName || '玩家'
      });
      // 使用邀请快照在本地预创建空房间，避免UI阻塞（不含对端音频连接）
      if (snapshot && snapshot.ownerId && roomId) {
        const existing = this._store.state.voiceChat.chatRooms.private.get(roomId);
        if (!existing) {
          this._store.commit('voiceChat/updateChatRoom', {
            type: 'private',
            roomId,
            data: {
              active: true,
              ownerId: snapshot.ownerId,
              participants: snapshot.participants || [snapshot.ownerId],
              createdAt: snapshot.createdAt || Date.now()
            }
          });
        }
      }
    } catch (e) {
      config.logger.error('处理私聊邀请失败:', e);
    }
  }

  /** 邀请回应（被邀请者端发起） */
  _handleVoicePrivateInviteResponse(params) {
    try {
      const { roomId, inviteeId, approved } = params || {};
      if (!roomId || !inviteeId) return;
      const room = this._store.state.voiceChat.chatRooms.private.get(roomId);

      // 兜底：若房主端没有本地房间（例如创建广播未及时回显），在同意时创建基础房间后再增量更新
      if (!room && approved) {
        // 仅说书人（房主）执行兜底创建
        if (!this._isCurrentUserStoryteller()) {
          const ownerId = this._store.state.session.playerId;
          this._store.commit('voiceChat/updateChatRoom', {
            type: 'private',
            roomId,
            data: {
              active: true,
              ownerId,
              participants: [ownerId],
              createdAt: Date.now()
            }
          });
          // 广播房间创建，确保各端建立同一房间基线
          this._send('voice_private_room_update', {
            roomId,
            action: 'created',
            ownerId,
            participants: [ownerId],
            createdAt: Date.now()
          });
        }
      }

      // 只有房主端负责最终的房间更新与广播
      const currentRoom = this._store.state.voiceChat.chatRooms.private.get(roomId);
      if (currentRoom && currentRoom.ownerId && this._store.state.session.playerId === currentRoom.ownerId) {
        // 记录待确认列表
        const pendingSet = this._pendingInviteApprovals || (this._pendingInviteApprovals = {});
        const list = pendingSet[roomId] || (pendingSet[roomId] = new Set());
        if (approved) list.add(inviteeId);
        // 通知邀请者收到回应（提示用）
        this._sendDirect(inviteeId, 'voice_private_join_response', {
          roomId,
          requesterId: inviteeId,
          approved
        });
        
        // 改为：每有一人同意，房主立即增量广播，让所有端逐步更新与连线
        if (approved) {
          const newParticipants = Array.from(new Set([...(currentRoom.participants || [currentRoom.ownerId]), inviteeId]));
          this._store.commit('voiceChat/updateChatRoom', {
            type: 'private',
            roomId,
            data: { participants: newParticipants }
          });
          // 房主本地立即同步UI（显示自己的私聊房间）
          if (window.voiceChatEventBus) {
            window.voiceChatEventBus.$emit('private-room-sync', {
              roomId,
              participants: newParticipants
            });
          }
          // 广播给所有客户端（以房主为准）
          this._send('voice_private_room_update', {
            roomId,
            action: 'participant_added',
            participantId: inviteeId,
            participants: newParticipants,
            ownerId: currentRoom.ownerId
          });
        }
      }
    } catch (e) {
      config.logger.error('处理私聊邀请回应失败:', e);
    }
  }

  /** 创建多人私聊房间并邀请目标 */
  _handleVoicePrivateCreateRoom(params) {
    try {
      const { roomId: clientRoomId, ownerId, targetIds = [] } = params || {};
      const selfId = this._store.state.session.playerId;
      // 非房主端：如果自己在目标名单中，仅展示邀请模态（不本地改房间参与者）
      if (selfId && selfId !== ownerId) {
        if (Array.isArray(targetIds) && targetIds.includes(selfId)) {
          const roomId = clientRoomId || `private_${Date.now()}_${Math.random().toString(36).slice(2,8)}`;
          const ownerName = this._store.state.players.players.find(p=>p.id===ownerId)?.name || '玩家';
          // 设置邀请模态
          this._store.commit('voiceChat/setIncomingInvite', {
            roomId,
            ownerId,
            ownerName
          });
        }
        return; // 非房主端到此结束
      }

      // 优先使用客户端生成的roomId，确保所有消息引用同一个ID
      const roomId = clientRoomId || `private_${Date.now()}_${Math.random().toString(36).slice(2,8)}`;
      // 房主本地建立房间，并广播房间创建
      this._store.commit('voiceChat/updateChatRoom', {
        type: 'private',
        roomId,
        data: {
          active: true,
          ownerId,
          participants: [ownerId],
          pendingInvites: targetIds,
          joinRequests: [],
          createdAt: Date.now(),
          joinPolicy: 'ownerApproval'
        }
      });

      // 本地立即触发一次UI同步，防止服务器不回显时房主界面不更新
      if (window.voiceChatEventBus) {
        window.voiceChatEventBus.$emit('private-room-created', {
          roomId,
          participants: [ownerId],
          createdAt: Date.now()
        });
        window.voiceChatEventBus.$emit('private-room-sync', {
          roomId,
          participants: [ownerId]
        });
      }

      this._send('voice_private_room_update', {
        roomId,
        action: 'created',
        ownerId,
        participants: [ownerId],
        pendingInvites: targetIds
      });

      // 向每个目标发送邀请（附带创建快照，方便客户端落地）
      const ownerName = this._store.state.players.players.find(p=>p.id===ownerId)?.name || '玩家';
      targetIds.forEach(targetId => {
        this._sendDirect(targetId, 'voice_private_invite', {
          roomId,
          ownerId,
          ownerName,
          snapshot: {
            ownerId,
            participants: [ownerId],
            createdAt: Date.now()
          }
        });
      });
    } catch (e) {
      config.logger.error('创建多人私聊房间失败:', e);
    }
  }

  /** 处理加入私聊申请（发送给房主） */
  _handleVoicePrivateJoinRequest(params) {
    try {
      const { roomId, requesterId } = params || {};
      const room = this._store.state.voiceChat.chatRooms.private.get(roomId);
      if (!room) return;
      // 仅房主处理
      if (room.ownerId && this._store.state.session.playerId === room.ownerId) {
        // 校验申请者是否已入座
        const players = this._store.state.players.players || [];
        const isSeated = !!players.find(p => p.id === requesterId);
        if (!isSeated) {
          // 直接拒绝并提示申请者需要入座
          this._sendDirect(requesterId, 'voice_private_join_response', {
            roomId,
            requesterId,
            approved: false,
            reason: 'not_seated'
          });
          return;
        }
        // 设置中心模态审批信息
        const requesterName = this._store.state.players.players.find(p=>p.id===requesterId)?.name || '玩家';
        this._store.commit('voiceChat/setJoinApproval', { roomId, requesterId, requesterName });
      }
    } catch (e) {
      config.logger.error('处理加入私聊申请失败:', e);
    }
  }

  /** 处理加入私聊申请的响应 */
  _handleVoicePrivateJoinResponse(params) {
    try {
      const { roomId, requesterId, approved, reason } = params || {};
      if (!roomId || !requesterId) return;
      const room = this._store.state.voiceChat.chatRooms.private.get(roomId);

      // 如果当前为房主且同意，房主端负责增量更新并广播（以房主为准）
      if (room && room.ownerId && this._store.state.session.playerId === room.ownerId) {
        if (approved) {
          const newParticipants = Array.from(new Set([...(room.participants || [room.ownerId]), requesterId]));
          this._store.commit('voiceChat/updateChatRoom', {
            type: 'private',
            roomId,
            data: { participants: newParticipants }
          });
          // 广播给所有客户端，由各端依广播更新本地显示
          this._send('voice_private_room_update', {
            roomId,
            action: 'participant_added',
            participantId: requesterId,
            participants: newParticipants,
            ownerId: room.ownerId
          });
        }
      }

      // 申请者端：无论房主端是否广播成功，都直接发给申请者结果和当前参与者列表，便于其本地加入
      if (room && room.ownerId) {
        this._sendDirect(requesterId, 'voice_private_join_response', {
          roomId,
          requesterId,
          approved,
          ownerId: room.ownerId,
          participants: (room.participants || []).concat(approved ? [requesterId] : [])
        });
      }
      
      // 本端为申请者时的提示与本地加入
      if (this._store.state.session.playerId === requesterId) {
        const ownerId = room?.ownerId;
        const ownerName = this._store.state.players.players.find(p=>p.id===ownerId)?.name || '房主';
        const rejectedMsg = reason === 'not_seated' ? '请先入座后再申请加入私聊房间' : `${ownerName} 拒绝了你的申请`;
        const msg = approved ? '房主已同意，请等待房主广播后加入房间' : rejectedMsg;
        this._store.commit('voiceChat/showUiBanner', msg);
        // 不再在申请者端本地立即加入，改为等待房主的 voice_private_room_update 广播
      }
    } catch (e) {
      config.logger.error('处理加入私聊响应失败:', e);
    }
  }

  /**
   * Handle voice private chat leave
   * @param params private chat leave data
   * @private
   */
  _handleVoicePrivateLeave(params) {
    try {
      config.logger.debug("收到私人语音聊天离开事件:", params);
      
      // 获取当前房间状态
      const currentRoom = this._store.state.voiceChat.chatRooms.private.get(params.roomId);
      if (currentRoom) {
        const newParticipants = currentRoom.participants.filter(id => id !== params.playerId);
        
        // 如果没有参与者了，关闭房间
        if (newParticipants.length === 0) {
          this._store.commit('voiceChat/updateChatRoom', {
            type: 'private',
            roomId: params.roomId,
            data: { active: false, participants: [] }
          });
        } else {
          // 更新参与者列表
          this._store.commit('voiceChat/updateChatRoom', {
            type: 'private',
            roomId: params.roomId,
            data: { participants: newParticipants }
          });
        }
      }
      
      config.logger.debug(`玩家 ${params.playerName} 离开了私人语音聊天房间 ${params.roomId}`);
      
      // 触发UI刷新事件，确保所有界面立即更新
      if (window.voiceChatEventBus) {
        window.voiceChatEventBus.$emit('private-chat-updated', {
          action: 'player_left',
          roomId: params.roomId,
          playerId: params.playerId,
          playerName: params.playerName
        });
      }
      
    } catch (error) {
      config.logger.error("处理私人语音聊天离开事件失败:", error);
    }
  }

  /**
   * Handle voice storyteller control commands
   * @param params storyteller control data
   * @private
   */
  _handleVoiceStorytellerControl(params) {
    try {
      config.logger.debug("收到说书人语音控制指令:", params);
      
      switch (params.action) {
        case 'global_mute': {
          // 说书人开启/关闭全局静音（广播）
          const muted = !!params.muted;
          this._store.commit('voiceChat/setGlobalPermissions', { globalMute: muted });
          // 给所有玩家显示显著提示
          this._send('voice_chat_notice', {
            type: 'global_mute',
            muted,
            message: muted ? '说书人已开启全员禁音' : '说书人已取消全员禁音'
          });
          config.logger.debug(`全局静音状态更新为: ${muted}`);
          break;
        }
        case 'mute_all':
          // 全体禁音
          params.playerIds.forEach(playerId => {
            this._store.commit('voiceChat/updatePlayerPermissions', {
              playerId,
              permissions: { isMuted: true }
            });
          });
          config.logger.debug(`说书人执行了全体禁音 (${params.playerIds.length} 人)`);
          break;
          
        case 'unmute_all':
          // 全体解除禁音
          params.playerIds.forEach(playerId => {
            this._store.commit('voiceChat/updatePlayerPermissions', {
              playerId,
              permissions: { isMuted: false }
            });
          });
          config.logger.debug(`说书人执行了全体解除禁音 (${params.playerIds.length} 人)`);
          break;
          
        case 'mute_player':
          // 单独禁音
          this._store.commit('voiceChat/updatePlayerPermissions', {
            playerId: params.playerId,
            permissions: { isMuted: true }
          });
          config.logger.debug(`说书人禁音了玩家 ${params.playerName || params.playerId}`);
          // 被禁言的一端本地弹出提示
          if (this._store.state.session.playerId === params.playerId) {
            this._store.commit('voiceChat/showUiBanner', '你已经被说书人禁言');
          }
          break;
        case 'unmute_player':
          // 单独解除禁音
          this._store.commit('voiceChat/updatePlayerPermissions', {
            playerId: params.playerId,
            permissions: { isMuted: false }
          });
          config.logger.debug(`说书人取消禁音了玩家 ${params.playerName || params.playerId}`);
          if (this._store.state.session.playerId === params.playerId) {
            this._store.commit('voiceChat/showUiBanner', '说书人已取消对你的禁言');
          }
          break;
          
        case 'clear_public':
          // 清空公共聊天
          this._store.commit('voiceChat/updateChatRoom', {
            type: 'public',
            data: { 
              participants: [],
              active: false
            }
          });
          config.logger.debug(`说书人清空了公共聊天 (${params.kickedPlayers.length} 人)`);
          break;
          
        default:
          config.logger.debug("未知的说书人控制指令:", params.action);
      }
    } catch (error) {
      config.logger.error("处理说书人语音控制指令失败:", error);
    }
  }

  /**
   * Handle voice speaking state updates
   * @param params speaking state data
   * @private
   */
  _handleVoiceSpeakingState(params) {
    try {
      // 这里我们需要更新Vue组件中的说话状态
      // 由于speaking状态在组件内部管理，我们通过事件总线或者直接操作
      
      // 触发自定义事件，让语音聊天组件监听
      if (window.voiceChatEventBus) {
        window.voiceChatEventBus.$emit('speaking-state-changed', {
          playerId: params.playerId,
          isSpeaking: params.isSpeaking,
          timestamp: params.timestamp
        });
      }
      
      // 语音状态变化日志已移除以减少控制台输出
    } catch (error) {
      config.logger.error("处理语音说话状态失败:", error);
    }
  }

  /**
   * Handle WebRTC signaling messages
   * @param params WebRTC signaling data
   * @private
   */
  _handleVoiceWebRTCSignal(params) {
    try {
      // WebRTC信令消息日志已移除以减少控制台输出
      
      // 触发自定义事件，让语音聊天组件处理
      if (window.voiceChatEventBus) {
        window.voiceChatEventBus.$emit('webrtc-signaling', {
          fromPlayerId: params.fromPlayerId,
          toPlayerId: params.toPlayerId,
          message: params.message
        });
      }
      
    } catch (error) {
      config.logger.error("处理WebRTC信令消息失败:", error);
    }
  }

  /**
   * Handle role updates from server
   */
  _handleVoiceRoleUpdate(params) {
    try {
      const { playerId, role } = params || {};
      if (!playerId || !role) return;
      // 存储角色映射
      this._store.commit('voiceChat/setRole', { playerId, role });
      // 将角色映射存入 voiceChat.displayNames 辅助判断显示（不改变名称，仅用于后续逻辑更准确）
      const labelMap = this._store.state.voiceChat.displayNames;
      if (role === 'storyteller') {
        // 若是说书人，使用统一显示名（无论其ID是否为host）
        const savedName = localStorage.getItem('storyteller_name') || '';
        const star = localStorage.getItem('storyteller_star_level') || 'trainee';
        const starIcon = { trainee: '🌟', one_star: '⭐', two_star: '⭐⭐' }[star] || '🌟';
        const dn = savedName.trim() ? `【${starIcon}】说书人·${savedName.trim()}` : `【${starIcon}】说书人`;
        this._store.commit('voiceChat/setDisplayName', { playerId, displayName: dn });
      } else if (!labelMap.has(playerId)) {
        // 初始化为访客占位，等待入座后由玩家名覆盖
        this._store.commit('voiceChat/setDisplayName', { playerId, displayName: '【访客】' });
      }
      // 也可在此处存一份角色表（如需）：
      // this._store.commit('voiceChat/setRole', { playerId, role }) // 若未来扩展
    } catch (e) {
      config.logger.error('处理角色更新失败:', e);
    }
  }

  /**
   * Handle roles snapshot on connect/reconnect
   */
  _handleVoiceRolesSnapshot(params) {
    try {
      const roles = (params && params.roles) || {};
      Object.keys(roles).forEach((pid) => {
        this._store.commit('voiceChat/setRole', { playerId: pid, role: roles[pid] });
        if (roles[pid] === 'storyteller') {
          const savedName = localStorage.getItem('storyteller_name') || '';
          const star = localStorage.getItem('storyteller_star_level') || 'trainee';
          const starIcon = { trainee: '🌟', one_star: '⭐', two_star: '⭐⭐' }[star] || '🌟';
          const dn = savedName.trim() ? `【${starIcon}】说书人·${savedName.trim()}` : `【${starIcon}】说书人`;
          this._store.commit('voiceChat/setDisplayName', { playerId: pid, displayName: dn });
        }
      });
    } catch (e) {
      config.logger.error('处理角色快照失败:', e);
    }
  }

  /**
   * Handle full voice state snapshot with revision
   */
  _handleVoiceStateSnapshot(params) {
    try {
      if (!params) return;
      // 1) 角色
      const roles = params.roles || {};
      Object.keys(roles).forEach((pid) => {
        this._store.commit('voiceChat/setRole', { playerId: pid, role: roles[pid] });
        if (roles[pid] === 'storyteller') {
          const savedName = localStorage.getItem('storyteller_name') || '';
          const star = localStorage.getItem('storyteller_star_level') || 'trainee';
          const starIcon = { trainee: '🌟', one_star: '⭐', two_star: '⭐⭐' }[star] || '🌟';
          const dn = savedName.trim() ? `【${starIcon}】说书人·${savedName.trim()}` : `【${starIcon}】说书人`;
          this._store.commit('voiceChat/setDisplayName', { playerId: pid, displayName: dn });
        }
      });
      // 2) 公聊
      if (params.publicChat) {
        this._store.commit('voiceChat/updateChatRoom', {
          type: 'public',
          data: {
            active: !!params.publicChat.active,
            participants: params.publicChat.participants || []
          }
        });
      }
      // 3) 私聊
      if (params.privateChats) {
        Object.entries(params.privateChats).forEach(([roomId, room]) => {
          this._store.commit('voiceChat/updateChatRoom', {
            type: 'private',
            roomId,
            data: {
              active: !!room.active,
              ownerId: room.ownerId,
              participants: room.participants || [],
              createdAt: room.createdAt || Date.now()
            }
          });
        });
      }
    } catch (e) {
      config.logger.error('处理语音状态快照失败:', e);
    }
  }

  /**
   * Handle users snapshot from server
   */
  _handleUsersSnapshot(params) {
    try {
      config.logger.debug('📋 收到用户管理数据快照:', params);
      this._store.commit('voiceChat/setUsersSnapshot', params);
      
      // 服务器用户数据已更新，记录当前角色
      const currentRole = this._getCurrentUserRole();
      if (currentRole) {
        config.logger.debug('✅ 服务器用户数据已同步，当前角色:', currentRole);
      }
    } catch (e) {
      config.logger.error('处理用户快照失败:', e);
    }
  }

  /**
   * Handle user joined event
   */
  _handleUserJoined(params) {
    try {
      config.logger.debug('👋 用户加入:', params);
      this._store.commit('voiceChat/updateUser', params);
    } catch (e) {
      config.logger.error('处理用户加入失败:', e);
    }
  }

  /**
   * Handle user left event
   */
  _handleUserLeft(params) {
    try {
      config.logger.debug('👋 用户离开:', params);
      if (params && params.playerId) {
        this._store.commit('voiceChat/removeUser', params.playerId);
      }
    } catch (e) {
      config.logger.error('处理用户离开失败:', e);
    }
  }

  /**
   * Handle user update event
   */
  _handleUserUpdate(params) {
    try {
      config.logger.debug('🔄 用户更新:', params);
      this._store.commit('voiceChat/updateUser', params);
      
      // 记录用户角色更新
      const currentRole = this._getCurrentUserRole();
      if (currentRole) {
        config.logger.debug('✅ 用户角色已更新:', currentRole);
      }
    } catch (e) {
      config.logger.error('处理用户更新失败:', e);
    }
  }

  /**
   * Handle rooms snapshot from server
   */
  _handleRoomsSnapshot(params) {
    try {
      config.logger.debug('🏠 收到房间管理数据快照:', params);
      this._store.commit('voiceChat/setRoomsSnapshot', params);
    } catch (e) {
      config.logger.error('处理房间快照失败:', e);
    }
  }

  /**
   * Handle room update event
   */
  _handleRoomUpdate(params) {
    try {
      config.logger.debug('🏠 房间更新:', params);
      this._store.commit('voiceChat/updateRoom', params);
    } catch (e) {
      config.logger.error('处理房间更新失败:', e);
    }
  }

  /**
   * Handle authoritative public chat updates from server
   */
  _handleVoicePublicUpdate(params) {
    try {
      if (!params) return;
      const participants = params.participants || [];
      config.logger.debug('📢 收到公聊权威更新:', params);
      this._store.commit('voiceChat/updateChatRoom', {
        type: 'public',
        data: {
          active: !!params.active,
          participants
        }
      });
      // 如果本端是host或roles表指示某个ID为storyteller，则显示名保持为说书人
      if (Array.isArray(participants)) {
        const roles = this._store.state.voiceChat.roles;
        participants.forEach(pid => {
          if ((roles && roles.get && roles.get(pid) === 'storyteller') || pid === 'host') {
            const savedName = localStorage.getItem('storyteller_name') || '';
            const star = localStorage.getItem('storyteller_star_level') || 'trainee';
            const starIcon = { trainee: '🌟', one_star: '⭐', two_star: '⭐⭐' }[star] || '🌟';
            const dn = savedName.trim() ? `【${starIcon}】说书人·${savedName.trim()}` : `【${starIcon}】说书人`;
            this._store.commit('voiceChat/setDisplayName', { playerId: pid, displayName: dn });
          }
        });
        // 清理：仅当该玩家并非说书人时，才移除“说书人”样式，避免误将非host的说书人改为访客
        const dnMap = this._store.state.voiceChat.displayNames;
        if (dnMap && dnMap.forEach) {
          dnMap.forEach((dn, pid) => {
            const isStoryteller = (roles && roles.get && roles.get(pid) === 'storyteller');
            if (!isStoryteller && pid !== 'host' && /说书人|storyteller/i.test(dn || '')) {
              this._store.commit('voiceChat/setDisplayName', { playerId: pid, displayName: '【访客】' });
            }
          });
        }
      }
    } catch (e) {
      config.logger.error('处理公聊更新失败:', e);
    }
  }

  /**
   * Handle voice private room update
   * @param params private room update data  
   * @private
   */
  _handleVoicePrivateRoomUpdate(params) {
    try {
      config.logger.debug("收到私聊房间更新:", params);
      
      if (params.action === 'created') {
        // 创建私聊房间
        this._store.commit('voiceChat/updateChatRoom', {
          type: 'private',
          roomId: params.roomId,
          data: {
            active: true,
            ownerId: params.ownerId || (this._store.state.voiceChat.chatRooms.private.get(params.roomId)?.ownerId),
            participants: params.participants,
            muted: false,
            createdAt: params.createdAt || Date.now()
          }
        });
        // 进入私聊的参与者不应继续留在公聊参与者列表中
        this._excludePrivateParticipantsFromPublic(params.participants || []);
        config.logger.debug(`私聊房间已创建: ${params.roomId}, 参与者: ${params.participants.join(', ')}`);
        
        // 发射私聊房间创建事件，让VoiceChatPanel处理音频连接和邀请信息
        if (window.voiceChatEventBus) {
          window.voiceChatEventBus.$emit('private-room-created', {
            roomId: params.roomId,
            participants: params.participants,
            createdAt: params.createdAt,
            inviteInfo: params.inviteInfo // 传递邀请信息
          });
          // 同时发射通用同步事件（用于后续参与者变更时复用）
          window.voiceChatEventBus.$emit('private-room-sync', {
            roomId: params.roomId,
            participants: params.participants
          });
        }
        
      } else if (params.action === 'closed') {
        // 关闭私聊房间
        this._store.commit('voiceChat/updateChatRoom', {
          type: 'private',
          roomId: params.roomId,
          data: {
            active: false
          }
        });
        config.logger.debug(`私聊房间已关闭: ${params.roomId}`);
        
      } else if (params.action === 'participant_removed') {
        // 处理参与者被移除（说书人抢夺玩家的情况）
        config.logger.debug(`处理参与者被移除: 房间 ${params.roomId}, 移除玩家 ${params.removedPlayerId}`);
        
        // 更新房间参与者列表
        this._store.commit('voiceChat/updateChatRoom', {
          type: 'private',
          roomId: params.roomId,
          data: {
            participants: params.remainingParticipants || []
          }
        });
        // 更新公聊列表：剩余仍在私聊的参与者从公聊移除；离开的参与者不做自动加入公聊
        this._excludePrivateParticipantsFromPublic(params.remainingParticipants || []);
        
        // 如果剩余参与者只有一个人，触发单人房间检查事件
        if (params.remainingParticipants && params.remainingParticipants.length === 1) {
          config.logger.debug(`房间 ${params.roomId} 只剩一个人，触发单人房间检查`);
          
          if (window.voiceChatEventBus) {
            window.voiceChatEventBus.$emit('check-alone-in-room', {
              roomId: params.roomId,
              remainingPlayerId: params.remainingParticipants[0]
            });
          }
        }
      } else if (params.action === 'owner_changed') {
        // 后端裁决房主转移，新房主接管房间
        const participants = params.participants || (this._store.state.voiceChat.chatRooms.private.get(params.roomId)?.participants) || [];
        this._store.commit('voiceChat/updateChatRoom', {
          type: 'private',
          roomId: params.roomId,
          data: {
            active: true,
            ownerId: params.ownerId,
            participants
          }
        });

        // 广播给本地UI进行音频与界面同步
        if (window.voiceChatEventBus) {
          window.voiceChatEventBus.$emit('private-room-sync', {
            roomId: params.roomId,
            participants
          });
        }

        // 若当前客户端成为新房主，给出提示
        if (this._store.state.session.playerId === params.ownerId) {
          this._store.commit('voiceChat/showUiBanner', '你已经成为房主');
        }
      } else if (params.action === 'participant_added') {
        // 有新参与者加入
        const existingRoom = this._store.state.voiceChat.chatRooms.private.get(params.roomId) || {};
        this._store.commit('voiceChat/updateChatRoom', {
          type: 'private',
          roomId: params.roomId,
          data: {
            active: existingRoom.active !== undefined ? existingRoom.active : true,
            ownerId: params.ownerId || existingRoom.ownerId,
            participants: params.participants || existingRoom.participants || []
          }
        });
        // 新加入私聊者不应出现在公聊参与者列表中（仅影响该加入者与房间内其他参与者）
        if (params.participants && Array.isArray(params.participants)) {
          this._excludePrivateParticipantsFromPublic(params.participants);
        }
        // 如果自己是被添加者，清理邀请模态
        if (window.voiceChatEventBus) {
          window.voiceChatEventBus.$emit('private-room-sync', {
            roomId: params.roomId,
            participants: params.participants || []
          });
        }
        if (this._store.state.session.playerId && params.participantId === this._store.state.session.playerId) {
          this._store.commit('voiceChat/setIncomingInvite', null);
        }
      }
      
    } catch (error) {
      config.logger.error("处理私聊房间更新失败:", error);
    }
  }

  /**
   * 从公聊参与者列表中剔除指定玩家（这些玩家已在私聊中）
   */
  _excludePrivateParticipantsFromPublic(playerIds) {
    try {
      if (!Array.isArray(playerIds) || playerIds.length === 0) return;
      const publicRoom = this._store.state.voiceChat.chatRooms.public || { participants: [] };
      const current = Array.isArray(publicRoom.participants) ? publicRoom.participants : [];
      const toRemove = new Set(playerIds);
      const filtered = current.filter(id => !toRemove.has(id));
      if (filtered.length !== current.length) {
        this._store.commit('voiceChat/updateChatRoom', {
          type: 'public',
          data: { participants: filtered, active: filtered.length > 0 }
        });
      }
    } catch (e) {
      config.logger.error('从公聊移除私聊参与者失败:', e);
    }
  }

  /**
   * Send a private message
   * @param message message data
   */
  sendPrivateMessage(message) {
    if (!this._socket || this._socket.readyState !== WebSocket.OPEN) {
      config.logger.warn("WebSocket连接未就绪，无法发送私聊消息", {
        hasSocket: !!this._socket,
        readyState: this._socket?.readyState
      });
      
      // 尝试重新连接
      if (this._store.state.session.sessionId) {
        config.logger.debug("尝试重新连接以发送消息...");
        this.connect(this._store.state.session.sessionId);
      }
      return false;
    }
    
    try {
      this._send("private_message", message);

      return true;
    } catch (error) {
      config.logger.error("发送私聊消息失败:", error);
      return false;
    }
  }

  /**
   * Claim a seat, needs to be confirmed by the Storyteller.
   * Seats already occupied can't be claimed.
   * @param seat either -1 to vacate or the index of the seat claimed
   */
  claimSeat(seat) {
    // 获取服务器端的用户数据
    const currentPlayerId = this._store.state.session.playerId;
    const currentUser = this._store.state.voiceChat.users?.get(currentPlayerId);
    const userRole = currentUser?.role || 'spectator';
    
    config.logger.debug("🪑 座位认领开始:", {
      seat,
      playerId: currentPlayerId,
      userRole: userRole,
      userPermissions: currentUser?.permissions,
      sessionId: this._store.state.session.sessionId
    });

    // 完全依赖服务器端用户数据判断权限
    if (!currentUser) {
      config.logger.warn("❌ 座位认领失败: 等待服务器用户数据");
      return;
    }
    
    if (userRole === 'storyteller') {
      config.logger.warn("❌ 座位认领失败: 说书人不能认领座位");
      return;
    }
    
    // 访客可以认领座位（认领后会自动升级为玩家）
    if (userRole === 'spectator') {
      config.logger.debug("🎯 访客认领座位，将自动升级为玩家");
    }

    const players = this._store.state.players.players;
    config.logger.debug("👥 当前玩家列表:", 
      players.map((p, i) => ({ 
        index: i, 
        name: p.name, 
        id: p.id,
        hasId: !!p.id
      }))
    );

    config.logger.debug("🔍 座位认领条件检查:", {
      playersLength: players.length,
      seat,
      seatInRange: players.length > seat,
      isVacateSeat: seat < 0,
      targetSeat: seat >= 0 ? players[seat] : null,
      targetSeatOccupied: seat >= 0 ? !!players[seat]?.id : false
    });

    const canClaim = players.length > seat && (seat < 0 || !players[seat].id);
    
    if (canClaim) {
      config.logger.debug(`✅ 认领座位 ${seat} 给玩家 ${this._store.state.session.playerId}`);
      config.logger.debug("📤 发送认领请求到服务器");
      this._send("claim", [seat, this._store.state.session.playerId]);
    } else {
      config.logger.error(`❌ 无法认领座位 ${seat}:`, {
        playersLength: players.length,
        seat,
        seatExists: seat < players.length,
        seatOccupied: seat >= 0 ? players[seat]?.id : false,
        reason: seat >= players.length ? "座位不存在" : 
                seat >= 0 && players[seat]?.id ? "座位已被占用" : "未知原因"
      });
    }
  }

  /**
   * Update a player id associated with that seat.
   * @param index seat index or -1
   * @param value playerId to add / remove
   * @private
   */
  _updateSeat([index, value]) {
    if (this._isCurrentUserStoryteller()) return;

    config.logger.debug("_updateSeat called:", { index, value });
    config.logger.debug("Current players:", this._store.state.players.players);

    const property = "id";
    const players = this._store.state.players.players;

    // remove previous seat
    const oldIndex = players.findIndex(({ id }) => id === value);
    if (oldIndex >= 0 && oldIndex !== index) {
      config.logger.debug(`Removing player from seat ${oldIndex}`);
      this._store.commit("players/update", {
        player: players[oldIndex],
        property,
        value: "",
      });
    }

    // add playerId to new seat
    if (index >= 0) {
      const player = players[index];
      if (!player) {
        config.logger.error(`No player at index ${index}`);
        return;
      }
      config.logger.debug(`Adding player ${value} to seat ${index} (${player.name})`);
      this._store.commit("players/update", { player, property, value });
      
      // 检查是否是当前用户认领了座位，如果是则自动升级为玩家
      const currentPlayerId = this._store.state.session.playerId;
      if (value === currentPlayerId) {
        config.logger.debug("🎯 当前用户成功认领座位，自动升级为玩家角色");
        
        // 更新hash角色标志位
        const sessionId = this._store.state.session.sessionId;
        if (sessionId) {
          const { setHashRoleFlag } = require("../utils/userStorage");
          setHashRoleFlag(sessionId, "player");
          config.logger.debug("✅ 已更新角色标志位为player");
        }
      }
    }

    // update player session list as if this was a ping
    this._handlePing([true, value, 0]);

    config.logger.debug("Seat update completed");
  }

  /**
   * Distribute player roles to all seated players in a direct message.
   * This will be split server side so that each player only receives their own (sub)message.
   */
  distributeRoles() {
    if (this._isCurrentUserStoryteller()) return;

    config.logger.debug("distributeRoles called");
    config.logger.debug("Current players:", this._store.state.players.players);

    const message = {};
    let validPlayers = 0;
    let totalPlayers = 0;

    this._store.state.players.players.forEach((player, index) => {
      totalPlayers++;
      config.logger.debug(`Player ${index}:`, {
        name: player.name,
        id: player.id,
        hasRole: !!player.role,
        roleId: player.role?.id,
        roleTeam: player.role?.team,
      });

      if (player.id && player.role && player.role.id) {
        // 只向对应玩家发送角色信息
        message[player.id] = [
          "player",
          { index, property: "role", value: player.role.id },
        ];
        validPlayers++;
        config.logger.debug(
          `Added player ${player.name} (${player.id}) with role ${player.role.id}`,
        );
      } else {
        config.logger.warn(`Player ${player.name} cannot receive role:`, {
          hasId: !!player.id,
          hasRole: !!player.role,
          hasRoleId: !!(player.role && player.role.id),
        });
      }
    });

    config.logger.debug(
      `Total players: ${totalPlayers}, Valid players: ${validPlayers}`,
    );
    config.logger.debug("Message to send:", message);

    if (Object.keys(message).length) {
      config.logger.debug("🚀 开始分发角色信息...");
      
      // 向每个玩家发送只包含自己角色的游戏状态
      Object.keys(message).forEach((playerId) => {
        config.logger.debug(`📤 向玩家 ${playerId} 发送游戏状态和角色信息`);
        this.sendGamestate(playerId, false, true);
      });
      
      // 备用方式：通过direct消息再次发送角色ID (为了确保角色信息被接收)
      config.logger.debug("📤 通过direct消息发送角色ID作为备用");
      this._send("direct", message);
      config.logger.debug("✅ 角色分发完成");
    } else {
      config.logger.error("❌ 没有有效的玩家可以分发角色!");
      config.logger.error("要求: player.id && player.role && player.role.id");
    }
  }

  /**
   * A player nomination. ST only
   * This also syncs the voting speed to the players.
   * Payload can be an object with {nomination} property or just the nomination itself, or undefined.
   * @param payload [nominator, nominee]|{nomination}
   */
  nomination(payload) {
    if (this._isCurrentUserStoryteller()) return;
    const nomination = payload ? payload.nomination || payload : payload;
    const players = this._store.state.players.players;
    if (
      !nomination ||
      (players.length > nomination[0] && players.length > nomination[1])
    ) {
      this.setVotingSpeed(this._store.state.session.votingSpeed);
      this._send("nomination", nomination);
    }
  }

  /**
   * Set the isVoteInProgress status. ST only
   */
  setVoteInProgress() {
    if (this._isCurrentUserStoryteller()) return;
    this._send("isVoteInProgress", this._store.state.session.isVoteInProgress);
  }

  /**
   * Send the isNight status. ST only
   */
  setIsNight() {
    if (this._isCurrentUserStoryteller()) return;
    this._send("isNight", this._store.state.grimoire.isNight);
  }

  /**
   * Send the isVoteHistoryAllowed state. ST only
   */
  setVoteHistoryAllowed() {
    if (this._isCurrentUserStoryteller()) return;
    this._send(
      "isVoteHistoryAllowed",
      this._store.state.session.isVoteHistoryAllowed,
    );
  }

  /**
   * Send the voting speed. ST only
   * @param votingSpeed voting speed in seconds, minimum 1
   */
  setVotingSpeed(votingSpeed) {
    if (this._isCurrentUserStoryteller()) return;
    if (votingSpeed) {
      this._send("votingSpeed", votingSpeed);
    }
  }

  /**
   * Set which player is on the block. ST only
   * @param playerIndex, player id or -1 for empty
   */
  setMarked(playerIndex) {
    if (this._isCurrentUserStoryteller()) return;
    this._send("marked", playerIndex);
  }

  /**
   * Clear the vote history for everyone. ST only
   */
  clearVoteHistory() {
    if (this._isCurrentUserStoryteller()) return;
    this._send("clearVoteHistory");
  }

  /**
   * Send a vote. Player or ST
   * @param index Seat of the player
   * @param sync Flag whether to sync this vote with others or not
   */
  vote([index]) {
    const player = this._store.state.players.players[index];
    if (
      this._store.state.session.playerId === player.id ||
      this._isCurrentUserStoryteller()
    ) {
      // send vote only if it is your own vote or you are the storyteller
      this._send("vote", [
        index,
        this._store.state.session.votes[index],
        this._isCurrentUserStoryteller(),
      ]);
    }
  }

  /**
   * Handle an incoming vote, but only if it is from ST or unlocked.
   * @param index
   * @param vote
   * @param fromST
   */
  _handleVote([index, vote, fromST]) {
    const { session, players } = this._store.state;
    const playerCount = players.players.length;
    const indexAdjusted =
      (index - 1 + playerCount - session.nomination[1]) % playerCount;
    if (fromST || indexAdjusted >= session.lockedVote - 1) {
      this._store.commit("session/vote", [index, vote]);
    }
  }

  /**
   * Lock a vote. ST only
   */
  lockVote() {
    if (this._isCurrentUserStoryteller()) return;
    const { lockedVote, votes, nomination } = this._store.state.session;
    const { players } = this._store.state.players;
    const index = (nomination[1] + lockedVote - 1) % players.length;
    this._send("lock", [this._store.state.session.lockedVote, votes[index]]);
  }

  /**
   * Update vote lock and the locked vote, if it differs. Player only
   * @param lock
   * @param vote
   * @private
   */
  _handleLock([lock, vote]) {
    if (!this._isCurrentUserStoryteller()) return;
    this._store.commit("session/lockVote", lock);
    if (lock > 1) {
      const { lockedVote, nomination } = this._store.state.session;
      const { players } = this._store.state.players;
      const index = (nomination[1] + lockedVote - 1) % players.length;
      if (this._store.state.session.votes[index] !== vote) {
        this._store.commit("session/vote", [index, vote]);
      }
    }
  }

  /**
   * Swap two player seats. ST only
   * @param payload
   */
  swapPlayer(payload) {
    if (this._isCurrentUserStoryteller()) return;
    this._send("swap", payload);
  }

  /**
   * Move a player to another seat. ST only
   * @param payload
   */
  movePlayer(payload) {
    if (this._isCurrentUserStoryteller()) return;
    this._send("move", payload);
  }

  /**
   * Remove a player. ST only
   * @param payload
   */
  removePlayer(payload) {
    if (this._isCurrentUserStoryteller()) return;
    this._send("remove", payload);
  }

  /**
   * Send reset message to all players
   * @param resetType "all" for storyteller reset, "player" for player reset
   */
  sendReset(resetType = "all", options = null) {
    config.logger.debug("发送重置消息:", resetType, options);
    if (this._socket && this._socket.readyState === 1) {
      const message = JSON.stringify(["reset", { type: resetType, options }]);
      this._socket.send(message);
      config.logger.debug("重置消息已发送");
    } else {
      config.logger.debug("Socket未连接，无法发送重置消息");
    }
  }

  /**
   * Handle reset message from storyteller
   * @param params reset parameters
   */
  _handleReset(params) {
    config.logger.debug("处理重置消息:", params);
    const { type, options = {} } = params;

    if (type === "all") {
      // 说书人重置所有玩家，按options执行
      config.logger.debug("执行说书人重置，选项:", options);
      const players = this._store.state.players.players;

      if (options.roles) {
        players.forEach((player) => {
          this._store.commit("players/update", {
            player,
            property: "role",
            value: { id: null, name: null },
          });
        });
      }
      if (options.bluffs) {
        this._store.commit("players/setBluff", {});
      }
      if (options.reminders) {
        players.forEach((player) => {
          this._store.commit("players/update", {
            player,
            property: "reminders",
            value: [],
          });
        });
      }
      if (options.deathStatus) {
        players.forEach((player) => {
          this._store.commit("players/update", {
            player,
            property: "isDead",
            value: false,
          });
        });
      }
      if (options.nominationStatus) {
        players.forEach((player) => {
          this._store.commit("players/update", {
            player,
            property: "isNominated",
            value: false,
          });
        });
      }
      if (options.aliveStatus) {
        players.forEach((player) => {
          this._store.commit("players/update", {
            player,
            property: "isDead",
            value: false,
          });
        });
      }
      if (options.gameHistory) {
        this._store.commit("clearHistory");
      }
      if (options.voteHistory) {
        this._store.commit("session/clearVoteHistory");
      }
      if (options.roundInfo) {
        // 可补充轮次重置逻辑
      }
      if (options.voteSettings) {
        this._store.commit("session/clearVoteHistory");
      }
      if (options.nightSettings) {
        this._store.commit("toggleNight", false);
      }
      if (options.markerSettings) {
        this._store.commit("session/setMarkedPlayer", -1);
      }
      if (options.seatOccupation) {
        // 可补充座位占用重置逻辑
      }
      if (options.playerIds) {
        // 可补充玩家ID重置逻辑
      }
      config.logger.debug("说书人重置所有玩家完成");
    } else if (type === "player") {
      // 玩家重置本地所有玩家数据（不发送网络消息）
      config.logger.debug("执行玩家本地重置所有玩家数据");

      // 重置所有玩家
      const players = this._store.state.players.players;
      players.forEach((player) => {
        this._store.commit("players/update", {
          player,
          property: "role",
          value: { id: null, name: null },
        });
        this._store.commit("players/update", {
          player,
          property: "reminders",
          value: [],
        });
        this._store.commit("players/update", {
          player,
          property: "isDead",
          value: false,
        });
        this._store.commit("players/update", {
          player,
          property: "isNominated",
          value: false,
        });
      });

      // 重置恶魔伪装
      this._store.commit("players/setBluff", {});

      // 重置游戏历史
      this._store.commit("clearHistory");

      // 重置投票相关状态
      this._store.commit("session/clearVoteHistory");
      this._store.commit("session/nomination", {
        nomination: false,
        votes: [],
        votingSpeed: 3000,
        lockedVote: 0,
        isVoteInProgress: false,
      });
      this._store.commit("session/setMarkedPlayer", -1);

      config.logger.debug("玩家本地重置所有玩家数据完成");
    }
  }
}

export default (store) => {
  // setup
  const session = new LiveSession(store);

  // 将socket实例暴露到store的state中
  store.state.socket = session;

  // listen to mutations
  store.subscribe(({ type, payload }, state) => {
    switch (type) {
      case "session/setSessionId":
        if (state.session.sessionId) {
          session.connect(state.session.sessionId);
        } else {
          config.logger.debug("🔥 WebSocket即将断开 - sessionId被清空:", {
            oldSessionId: state.session.sessionId,
            newSessionId: payload,
            stackTrace: new Error().stack
          });
          window.location.hash = "";
          session.disconnect();
        }
        break;
      case "session/claimSeat":
        session.claimSeat(payload);
        break;
      case "session/distributeRoles":
        if (payload) {
          session.distributeRoles();
        }
        break;
      case "session/nomination":
      case "session/setNomination":
        session.nomination(payload);
        break;
      case "session/setVoteInProgress":
        session.setVoteInProgress(payload);
        break;
      case "session/voteSync":
        session.vote(payload);
        break;
      case "session/lockVote":
        session.lockVote();
        break;
      case "session/setVotingSpeed":
        session.setVotingSpeed(payload);
        break;
      case "session/clearVoteHistory":
        session.clearVoteHistory();
        break;
      case "session/setVoteHistoryAllowed":
        session.setVoteHistoryAllowed();
        break;
      case "toggleNight":
        session.setIsNight();
        break;
      case "setEdition":
        session.sendEdition();
        break;
      case "players/setFabled":
        session.sendFabled();
        break;
      case "session/setMarkedPlayer":
        session.setMarked(payload);
        break;
      case "players/swap":
        session.swapPlayer(payload);
        break;
      case "players/move":
        session.movePlayer(payload);
        break;
      case "players/remove":
        session.removePlayer(payload);
        break;
      case "players/set":
      case "players/clear":
      case "players/add":
        // 只有在socket连接建立且不在初始化阶段时才发送游戏状态
        if (
          session._socket &&
          session._socket.readyState === 1 &&
          !session._isInitializing
        ) {
          session.sendGamestate("", true);
        }
        break;
      case "players/update":
        if (payload.property === "pronouns") {
          session.sendPlayerPronouns(payload);
        } else if (payload.property === "role") {
          // 角色更新不自动发送，只在distributeRoles时发送
          config.logger.debug("角色更新，但不自动发送:", payload);
        } else {
          session.sendPlayer(payload);
        }
        break;
    }
  });

  // check for session Id in hash (only on initial load)
  const sessionId = window.location.hash.substr(1);
  if (sessionId && !store.state.session.sessionId) {
    // 只有在没有现有会话时才自动设置
    const {
      getHashRoleFlag,
      setHashRoleFlag,
    } = require("../utils/userStorage");
    const hashRole = getHashRoleFlag(sessionId);

    if (hashRole === "storyteller") {
      // 说书人模式
      config.logger.debug("检测到说书人hash标志位，设置为说书人模式");
      store.commit("session/setSessionId", sessionId);
      store.commit("toggleGrimoire", true);
    } else if (hashRole === "player") {
      // 玩家模式（已认领座位）
      config.logger.debug("检测到玩家hash标志位，设置为玩家模式");
      store.commit("session/setSessionId", sessionId);
      store.commit("toggleGrimoire", false);
    } else if (hashRole === "spectator") {
      // 访客模式
      config.logger.debug("检测到访客hash标志位，设置为访客模式");
      store.commit("session/setSessionId", sessionId);
      store.commit("toggleGrimoire", false);
    } else {
      // 没有标志位，默认为说书人模式（第一个创建房间的用户）
      config.logger.debug("没有hash角色标志位，默认设置为说书人(第一个创建者)模式");
      store.commit("session/setSessionId", sessionId);
      setHashRoleFlag(sessionId, "storyteller");
      store.commit("toggleGrimoire", true);
    }
  }

  // 确保在数据恢复完成后重新建立socket连接
  // 使用setTimeout确保在下一个事件循环中执行
  setTimeout(() => {
    if (store.state.session.sessionId && !session._socket) {
      config.logger.debug("数据恢复完成，重新建立socket连接");
      session.connect(store.state.session.sessionId);
    }
  }, 100);
};
